/*
 * 格式化器类
 *
 * 使用格式化子项保存各个子项的格式化方法，
 * 然后交给格式化器按规则串指定的顺序格式化出日志消息字符串。
 *
 **/
#pragma once

#include <iostream>
#include <memory>
#include <vector>
#include <sstream>
#include "message.hpp"
#include "config.hpp"

namespace easylog
{
    struct format_item
    {
        using ptr = std::shared_ptr<format_item>;
        virtual void format(std::ostream& out, const message& msg) = 0;
    };

    struct time_format_item : public format_item
    {
        time_format_item(const std::string& fmt) : _fmt(fmt)
        {}

        virtual void format(std::ostream& out, const message& msg) override
        {
            out << util::time::format(msg.time, _fmt);
        }

        std::string _fmt;
    };

    // used to format tabs
    struct tab_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << '\t';
        }
    };

    // used to format thread id
    struct tid_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            // Output tid in hex, then convert back to decimal.
            out << std::hex << std::showbase << msg.tid << std::dec;
        }
    };

    struct level_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << level::level2str(msg.levo);
        }
    };

    // used to format logger name
    struct lname_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << msg.lname;
        }
    };

    struct file_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << msg.file;
        }
    };

    struct line_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << msg.line;
        }
    };

    struct func_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << msg.func;
        }
    };

    struct pload_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << msg.payload;
        }
    };

    // used to format line feed
    struct lfeed_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << '\n';
        }
    };

    // used to format percentages.
    struct hcent_format_item : public format_item
    {
        virtual void format(std::ostream& out, const message& msg) override
        {
            out << '%';
        }
    };

    // format item for serial number, used in the toggles of toggle file name.
    struct order_format_item : public format_item
    {
        order_format_item(const std::string& n) : _n(n)
        {}

        virtual void format(std::ostream& out, const message& msg) override
        {
            out << _n;
        }

        std::string _n;
    };

    struct other_format_item : public format_item
    {
        other_format_item(const std::string& other) : _other(other)
        {}

        virtual void format(std::ostream& out, const message& msg) override
        {
            out << _other;
        }

        std::string _other;
    };

    class formatter
    {
    public:
        using ptr = std::shared_ptr<formatter>;

    public:
        formatter(const std::string& pattern = config::instance().get_pattern())
            : _pattern(pattern)
        {
            parser();
        }

        // This constructor is only provided for the formatting of toggle file's filename.
        // The filename pattern and the value of order need to be provided.
        formatter(const std::string& pattern, size_t num)
            : _pattern(pattern)
        {
            parser(num);
        }

        // external interface for formatting message object.
        std::string format(message&& msg)
        {
            std::stringstream ss;

            for (auto& item : _items)
                item->format(ss, std::forward<message>(msg));

            return ss.str();
        }

        void set_pattern(const std::string& pattern)
        {
            _pattern = pattern;
            parser();
        }

        formatter(const formatter&) = default;
        formatter(formatter&&) = default;

        formatter& operator=(const formatter&) = default;
        formatter& operator=(formatter&&) = default;

    private:
        // Define format control characters.
        enum specifier
        {
            time  = 'd',
            tab   = 'T',
            tid   = 't',
            level = 'p',
            lname = 'c',
            file  = 'f',
            line  = 'l',
            func  = 'a',
            pload = 'm',
            lfeed = 'n',
            hcent = '%',
            order = 'i',
            other =  -1,
        };

        // [%d{%F %H:%M:%S}][%p][%t][%c][%f:%l]%T%m%n
        //
        // Parse the formatted rule string provided by users,
        // and append the correspond format items into the items array.
        bool parser(size_t num = 0)
        {
            _items.clear();

            size_t i = 0;
            while (i < _pattern.size())
            {
                if (_pattern[i] == '%') // Format item encountered.
                {
                    ++i;
                    if (_pattern.size() > i) // Ensure there are characters after %.
                    {
                        if (_pattern[i] == time) // Parse time format item.
                        {
                            size_t b = i + 1;
                            size_t e = b + 1;

                            if (_pattern.size() > b && _pattern[b] == '{')
                            {
                                while (e < _pattern.size() && _pattern[e] != '}')
                                    ++e;

                                if (e >= _pattern.size() || _pattern[e] != '}')
                                    throw except("formatter::paser: time parser error, no }");

                                append_item(time, _pattern.substr(b + 1, e - b - 1));
                                i = e + 1;
                            }
                            else
                            {
                                throw except("timme parser error, no {");
                            }
                        }
                        else if (_pattern[i] == order) // File name pattern of toggling file can also be considered as a pattern,
                                                       // and can also be handed for parsing.
                                                       // Therefore, %i is used only for format file name of toggling files.
                        {
                            append_item(_pattern[i], std::to_string(num)); // %i represents the order, which is param num.
                            ++i;
                        }
                        else if (isctrlkey(_pattern[i])) // Apart from time and order , others will process here.
                        {
                            append_item(_pattern[i]);
                            ++i;
                        }
                        else
                        {
                            throw except("no format control character behind %");
                        }
                    }
                    else throw except("parser error, nothing behind %");
                }
                else // ordinary characters encountered, not specifier
                {
                    append_item(other, std::string(1, _pattern[i]));
                    ++i;
                }
            }
            return true;
        }

        // Insert items correspond to specifier into the format item array.
        void append_item(const char key, const std::string& val = "")
        {
            if      (key == time)  _items.push_back(std::make_shared<time_format_item>(val));
            else if (key == tab)   _items.push_back(std::make_shared<tab_format_item>());
            else if (key == tid)   _items.push_back(std::make_shared<tid_format_item>());
            else if (key == level) _items.push_back(std::make_shared<level_format_item>());
            else if (key == lname) _items.push_back(std::make_shared<lname_format_item>());
            else if (key == file)  _items.push_back(std::make_shared<file_format_item>());
            else if (key == line)  _items.push_back(std::make_shared<line_format_item>());
            else if (key == func)  _items.push_back(std::make_shared<func_format_item>());
            else if (key == pload) _items.push_back(std::make_shared<pload_format_item>());
            else if (key == lfeed) _items.push_back(std::make_shared<lfeed_format_item>());
            else if (key == hcent) _items.push_back(std::make_shared<hcent_format_item>());
            else if (key == order) _items.push_back(std::make_shared<order_format_item>(val));
            else if (key == other) _items.push_back(std::make_shared<other_format_item>(val));
            else throw except("formatter::append_item: unexpected key");
        }

        bool isctrlkey(const char key)
        {
            return key == time
                || key == tab
                || key == tid
                || key == level
                || key == lname
                || key == file
                || key == line
                || key == func
                || key == pload
                || key == lfeed
                || key == hcent
                || key == order;
        }

    private:
        std::string _pattern;
        std::vector<format_item::ptr> _items;
    };

}
